/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package session;

import event.SendEvent;
import event.StatusEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import net.sf.appia.core.AppiaEventException;
import net.sf.appia.core.Channel;
import net.sf.appia.core.Direction;
import net.sf.appia.core.Event;
import net.sf.appia.core.Layer;
import net.sf.appia.core.events.channel.ChannelInit;
import net.sf.appia.protocols.common.RegisterSocketEvent;
import util.Constants;
import util.CustomProcess;
import util.Debug;

/**
 *
 * @author Welington
 */
public class AppSession extends CustomSession {

    private ApplicationDataReader reader = null;
    private HashMap<Integer, CustomProcess> processes;
    private CustomProcess sourceProcess;

    public AppSession(Layer layer) {
        super(layer);
        current = getClass();
    }

    @Override
    public void handle(Event event) {
        if (event instanceof ChannelInit) {
            handleInit((ChannelInit) event);
        } else if (event instanceof SendEvent) {
            if (event.getDir() == Direction.UP) {
                handleDeliveryEvent((SendEvent) event);
            }
        } else if (event instanceof RegisterSocketEvent) {
            handleRegisterSocket((RegisterSocketEvent) event);
        } else if (event instanceof StatusEvent) {
            handleStatusEvent((StatusEvent) event);
        }
    }

    @Override
    protected void handleInit(ChannelInit event) {
        debug.print(Debug.LOG, current, "handleInit(ChannelInit event)");
        sendEvent(event);

        try {
            /* Identifying the current process */
            processes = Constants.readFromFile();
            sourceProcess = Constants.SOURCE_PROCESS;

            debug.print(Debug.INFO, current, "Current process correctly identified {" + sourceProcess.getId() + " - " + sourceProcess.getLabel() + "}");

            RegisterSocketEvent rse = new RegisterSocketEvent(event.getChannel(), Direction.DOWN, this, sourceProcess.getPort());

            try {
                rse.localHost = InetAddress.getLocalHost();
            } catch (UnknownHostException ex) {
                debug.print(Debug.LOG, current, ex.getLocalizedMessage());
            }

            initEvent(rse);
            sendEvent(rse);
        } catch (AppiaEventException ex) {
            debug.print(Debug.LOG, current, ex.getLocalizedMessage());
        }
    }

    private void handleDeliveryEvent(SendEvent event) {
        debug.print(Debug.LOG, current, "handleDeliveryEvent(DeliveryEvent event)");

        /* Open message */
        debug.print(Debug.INFO, current, "Message delivered by lower layer");
        debug.print(Debug.INFO, current, event.getMessageContent());
    }

    private void handleRegisterSocket(RegisterSocketEvent event) {
        debug.print(Debug.LOG, current, "handleRegisterSocket(RegisterSocketEvent event)");

        if (!event.error) {
            debug.print(Debug.INFO, current, "Registering socket succeded");

            if (reader == null) {
                reader = new ApplicationDataReader(event.getChannel());
            }
        } else {
            debug.print(Debug.ERROR, current, "Registering socket failed - " + event.getErrorDescription());
        }
    }

    private void handleStatusEvent(StatusEvent event) {
        debug.print(Debug.LOG, current, "handleStatusEvent(StatusEvent event)");

        /* Open message */
        debug.print(Debug.INFO, current, "Status delivered by lower layer");
        debug.print(Debug.INFO, current, event.getMessage());
    }

    private class ApplicationDataReader extends Thread {

        public boolean ready = false;
        public Channel channel;
        private BufferedReader stdin;
        private int rid = 0;

        public ApplicationDataReader(Channel channel) {
            stdin = new BufferedReader(new InputStreamReader(System.in));
            ready = true;
            if (this.channel == null) {
                this.channel = channel;
            }
            this.start();
        }

        @Override
        public void run() {
            boolean running = true;
            CustomProcess destinationProcess = null;

            debug.print(Debug.INFO, current, "With what process do you want to communicate?");

            for (CustomProcess p : processes.values()) {
                debug.print(Debug.INFO, current, "{" + p.getId() + " - " + p.getLabel() + "}");
            }

            do {
                String data;
                int pid = 0;
                try {
                    destinationProcess = null;
                    data = stdin.readLine();

                    try {
                        pid = Integer.parseInt(data);

                        destinationProcess = processes.get(pid);

                        if (destinationProcess == null) {
                            debug.print(Debug.WARN, current, "Invalid PID. Please, type a valid process ID");
                        }
                    } catch (NumberFormatException ex) {
                        debug.print(Debug.WARN, current, "Invalid PID. Please, type a valid process ID");
                        pid = -1;
                    }

                } catch (IOException ex) {
                    debug.print(Debug.ERROR, current, ex.getLocalizedMessage());
                }
            } while (destinationProcess == null);

            while (running) {
                ++rid;
                try {
                    debug.print(Debug.INFO, current, "HANDLING REQUEST [" + rid + "] - Type the message");
                    String data = stdin.readLine();

                    SendEvent event = new SendEvent(rid);
                    event.dest = destinationProcess.getAddress();
                    event.setMessage(data);
                    sendAsyncEvent(event, channel, Direction.DOWN);
                } catch (IOException e) {
                    debug.print(Debug.ERROR, current, e.getLocalizedMessage());
                }

                try {
                    Thread.sleep(1500);
                } catch (Exception ex) {
                    debug.print(Debug.ERROR, current, ex.getLocalizedMessage());
                }

                synchronized (this) {
                    if (!ready) {
                        running = false;
                    }
                }
            }
        }
    }
}
